package ar.edu.untref.lp1.plomeria.controladores;

import ar.edu.untref.lp1.plomeria.interfaces.Plomero;
import ar.edu.untref.lp1.plomeria.modelos.*;

/**
 * 
 * @author claudiojd@gmail.com
 * 
 *         TP1 para Lenguajes de Programacion 1 UNTREF 2012
 * 
 *         Un objeto instancia de PlomeroMatic recibe una pared (Material[][]) y
 *         ejecutando el metodo construirCañeria(Material[][] material) une los
 *         caños horizontales que contenga. Las condiciones que debe cumplir la
 *         pared se encuentran definidas en la declaracion de la interface
 *         Plomero.
 * 
 */
public class PlomeroMatic implements Plomero {

	private Tablero paredEnConstruccion;
	private Material[][] paredEncargadaArrayOriginal;

	/**
	 * Implementa el metodo definido en la interfase Plomero. Es el metodo de
	 * entrada, a ser invocado para resolver el problema de tirar caños que unan
	 * todas las tuberias en la pared.
	 * 
	 * PRE: una matriz que se respete [columnas][filas] y la distribucion de
	 * caños cumpla con los requisitos del TP, esta matriz representa una pared.
	 * 
	 * @param Material
	 *            [][] pared
	 */
	public void construirCañeria(Material[][] pared) {

		this.declararParedDeTrabajo(pared);

		this.algoritmoDeResolucionDelProblema();

		this.copiaLaSolucionSobreLaParedOriginal();
	}

	private void copiaLaSolucionSobreLaParedOriginal() {
		
		Material[][] matrizDelTablero = this.getTrabajo();
		for (int columna = 0; columna < this.paredEncargadaArrayOriginal.length ; columna++) {
			for (int fila = 0; fila < this.paredEncargadaArrayOriginal[0].length; fila++) {
				this.paredEncargadaArrayOriginal[columna][fila] = matrizDelTablero[columna][fila];
			}
		}

	}

	/**
	 * PRE: recibe una matriz Material[][]
	 * 
	 * POST: crea un nuevo objeto tablero para operar la solucion, y lo asigna
	 * al atributo pared.
	 * 
	 * @param Material
	 *            [][] pared Matriz bidimensional de enum Material, representa
	 *            el modelo de pared a resolver.
	 */
	public void declararParedDeTrabajo(Material[][] pared) {

		this.paredEncargadaArrayOriginal = pared;
		this.paredEnConstruccion = new Tablero(pared);
	}

	/**
	 * aqui se resuelve el CORE del problema.
	 * 
	 * PRE: se requiere un tablero (pared) valido en las condiciones del
	 * practico.
	 * 
	 * POST: Se determinan las secciones que se deben unir, encontrando los
	 * caños extremos, y entonces considera cada tramos como una sub pared, un
	 * caso simple de solo dos caños horizontales dentro de la pared. Entonces
	 * llama al metodo que determina que estrategia debe utilizar y la aplica a
	 * la pared.
	 * 
	 */
	public void algoritmoDeResolucionDelProblema() {

		Casillero cañoIzquierdo = this.paredEnConstruccion.getCasillero(0, 0);
		Casillero cañoDerecho;
		/**
		 * Asigno el primer caño, el izquierdo
		 */
		while (cañoIzquierdo.getFila() < this.paredEnConstruccion.getFilas()
				&& cañoIzquierdo.getMaterial() != Material.CAÑO_HORIZONTAL) {
			cañoIzquierdo = this.paredEnConstruccion
					.getSiguienteCasilleroDebajo(cañoIzquierdo);
		}

		/*
		 * Inicio la busqueda del segundo caño, el de la derecha. esto da la
		 * primera casilla de la siguiente columna, o null si estoy en la ultima
		 * columna.
		 */
		cañoDerecho = this.paredEnConstruccion
				.getPrimerCasilleroDeSiguienteColumna(cañoIzquierdo);

		/*
		 * Terminado el setup, iniciamos el proceso principal de resolucion del
		 * caso
		 */

		/* verifico si estoy en la ultima columna de la derecha */
		while (cañoDerecho != null
				&& cañoDerecho.getColumna() < this.paredEnConstruccion
						.getColumnas()) {

			/* recorro la columna hasta el fin buscando el caño de la derecha. */
			while (cañoDerecho.getFila() < this.paredEnConstruccion.getFilas()
					&& cañoDerecho.getMaterial() != Material.CAÑO_HORIZONTAL) {
				cañoDerecho = this.paredEnConstruccion
						.getSiguienteCasilleroDebajo(cañoDerecho);
			}
			if (cañoDerecho != null
					&& cañoDerecho.getMaterial() == Material.CAÑO_HORIZONTAL) {
				this.amurarCaños(cañoIzquierdo, cañoDerecho);
				cañoIzquierdo = cañoDerecho;
				cañoDerecho = this.paredEnConstruccion
						.getPrimerCasilleroDeSiguienteColumna(cañoIzquierdo);

			}
		}
	}

	/**
	 * Estrategia unitaria de resolucion, trabaja considerandos solo las
	 * columnas involucradas en el problema de la seccion de pared recibida como
	 * parametro.
	 * 
	 * PRE: recibe el tablero y dos casilleros con caño horizontal en el
	 * tablero, con al menos una columna intermedia vacia. POST: une los dos
	 * caños horizontales modificando materiales ladrillo por caño en el tablero
	 * 
	 * @param cañoIzquierdo
	 *            determina la columna de menor indice involucrada
	 * @param cañoDerecho
	 *            determina la columna de mayor indice involucrada
	 */
	public void amurarCaños(Casillero cañoIzquierdo, Casillero cañoDerecho) {

		/**
		 * valido que caño esta mas alto, y aplico la estrategia que
		 * corresponda.
		 */
		int diferenciadeFilas = cañoIzquierdo.getFila() - cañoDerecho.getFila();

		if (diferenciadeFilas > 0) {
			this.unirDeAbajoHaciaArriba(cañoIzquierdo, cañoDerecho);
		} else if (diferenciadeFilas < 0) {
			this.unirDeArribaHaciaAbajo(cañoIzquierdo, cañoDerecho);
		} else {
			this.ambosCañosEstanEnLaMismaFila(cañoIzquierdo, cañoDerecho);
		}
	}

	/**
	 * PRE: recibe dos casilleros del tablero que determinan una seccion de
	 * pared, en la que se deben unir los caños horizontales de las columnas
	 * extremas.
	 * 
	 * POST: setea los caños sobre ladrillo uniendo los caños horizontales
	 * extremos, en forma descendente.
	 * 
	 * @param cañoDerecho
	 * @param cañoIzquierdo
	 */
	public void unirDeArribaHaciaAbajo(Casillero cañoIzquierdo,
			Casillero cañoDerecho) {

		validaSeparacionMinimaEntreDosCañosHorizontales(cañoIzquierdo,
				cañoDerecho);

		int columna = cañoIzquierdo.getColumna() + 1;
		int fila = cañoIzquierdo.getFila();

		/* codo abajo izquierda */
		this.paredEnConstruccion.setMaterialCasillero(
				Material.CAÑO_ABAJO_IZQUIERDA, columna, fila);
		fila++;
		while (fila < cañoDerecho.getFila()) {
			this.paredEnConstruccion.setMaterialCasillero(
					Material.CAÑO_VERTICAL, columna, fila);
			fila++;
		}
		this.paredEnConstruccion.setMaterialCasillero(
				Material.CAÑO_ARRIBA_DERECHA, columna, fila);
		columna++;
		while (columna < cañoDerecho.getColumna()) {
			this.paredEnConstruccion.setMaterialCasillero(
					Material.CAÑO_HORIZONTAL, columna, fila);
			columna++;
		}
	}

	/**
	 * PRE: recibe dos casilleros del tablero que determinan una seccion de
	 * pared, en la que se deben unir los caños horizontales de las columnas
	 * extremas.
	 * 
	 * POST: setea los caños sobre ladrillo uniendo los caños horizontales
	 * extremos, en forma descendente.
	 * 
	 * @param cañoDerecho
	 * @param cañoIzquierdo
	 */
	public void unirDeAbajoHaciaArriba(Casillero cañoIzquierdo,
			Casillero cañoDerecho) {

		validaSeparacionMinimaEntreDosCañosHorizontales(cañoIzquierdo,
				cañoDerecho);

		int columna = cañoIzquierdo.getColumna() + 1;
		int fila = cañoIzquierdo.getFila();

		/* codo abajo izquierda */
		this.paredEnConstruccion.setMaterialCasillero(
				Material.CAÑO_ARRIBA_IZQUIERDA, columna, fila);
		fila--;
		while (fila > cañoDerecho.getFila()) {
			this.paredEnConstruccion.setMaterialCasillero(
					Material.CAÑO_VERTICAL, columna, fila);
			fila--;
		}
		this.paredEnConstruccion.setMaterialCasillero(
				Material.CAÑO_ABAJO_DERECHA, columna, fila);
		columna++;
		while (columna < cañoDerecho.getColumna()) {
			this.paredEnConstruccion.setMaterialCasillero(
					Material.CAÑO_HORIZONTAL, columna, fila);
			columna++;
		}

	}

	/**
	 * PRE: recibe dos casilleros a la misma altura (fila) separados por al
	 * menos una columna vacia POST: une los casilleros intermedios en linea
	 * recta con caños horizontales.
	 * 
	 * @param Casillero
	 *            cañoIzquierdo
	 * @param Casillero
	 *            cañoDerecho
	 */
	public void ambosCañosEstanEnLaMismaFila(Casillero cañoIzquierdo,
			Casillero cañoDerecho) {

		validaSeparacionMinimaEntreDosCañosHorizontales(cañoIzquierdo,
				cañoDerecho);

		int columna = cañoIzquierdo.getColumna() + 1;
		while (columna < cañoDerecho.getColumna()) {
			this.paredEnConstruccion.setMaterialCasillero(
					Material.CAÑO_HORIZONTAL, columna, cañoIzquierdo.getFila());
			columna++;
		}
	}

	/**
	 * POST: valida que los dos casillero recibidos tengan al menos una columna
	 * intermedia, o sea, nos ean contiguos.
	 * 
	 * @param cañoDerecho
	 * @param cañoIzquierdo
	 * @throws Error
	 */
	private void validaSeparacionMinimaEntreDosCañosHorizontales(
			Casillero cañoIzquierdo, Casillero cañoDerecho) throws Error {
		if (cañoDerecho.getColumna() - cañoIzquierdo.getColumna() < 2) {
			Error separacionInsuficienteEntreCaños = new Error(
					"La separacion entre los caños a unir es insuficiente, el caño izquierdo esta en columna "
							+ cañoIzquierdo.getColumna()
							+ " y el caño de la derecha esta en columna "
							+ cañoDerecho.getColumna()
							+ ", verifique la matriz utilizada.");
			throw separacionInsuficienteEntreCaños;
		}
	}

	/**
	 * POST: retorna la matriz de Material[][] que representa el objeto tablero.
	 * 
	 * @return Material[][]
	 */
	public Material[][] getTrabajo() {
		return this.paredEnConstruccion.getMatrizDelTablero();
	}

	/**
	 * Post: Tablero con algoritmo terminado.
	 * 
	 * @return Tablero tablero
	 */
	public Tablero getTableroTerminado() {
		return this.paredEnConstruccion;
	}
}
